package com.mall.util.distributed.id;

import org.apache.commons.lang3.RandomUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.SystemUtils;

import java.net.Inet4Address;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

/**
 * !---无符号数---!---时间戳---!---工作id---!---序列号---!
 * !-----1------!-----41----!-----10----!-----12----!
 * <p>
 * ||
 * <p>
 * !---无符号数---!---时间戳---!---机器id---!---数据编码---!---序列号---!
 * !-----1------!-----41----!-----5----!-----5----!-----12----!
 */
public class SnowFlowerIdGenerator implements IdGenerator {

    /**
     * Start time intercept (2018-08-05 08:34)
     */
    public static final long EPOCH = 1533429240000L;

    /**
     * 对应各自的大小
     */
    public static final int workIdBits = 5;
    public static final int dataCenterIdBits = 5;
    public static final int sequencebits = 12;

    /**
     * 各自对应的位置
     */
    public static final int dataCenterShift = sequencebits;
    public static final int workIdShift = sequencebits + dataCenterIdBits;
    public static final int timestampShift = sequencebits + workIdBits + dataCenterIdBits;

    /**
     * 最大允许多少
     */
    public static final long maxWorkerId = -1L ^ (-1L << workIdBits);   // 32
    public static final long maxDataCenterId = -1L ^ (-1L << dataCenterIdBits); // 32
    public static final long maxSequence = -1L ^ (-1L << sequencebits); // 4095

    /**
     * 最大能容忍的时间回拨
     */
    public static final int MAX_BACKWARD_MS = 3;

    public long workerId;
    public long dataCenterId;
    public long sequence;
    public long lastTime;
    public long currentId;

    {  /* 生成workId,应该获取网络IP */
        workerId = getWorkerId();
        dataCenterId = getDataCenterId();

        init();
    }

    @Override
    public void init() {
        initialize(workerId, dataCenterId);
    }

    public void initialize(long workerId, long dataCenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0, current workId %d",
                            maxWorkerId, workerId));
        }
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(
                    String.format("worker Id can't be greater than %d or less than 0, current workId %d",
                            maxDataCenterId, dataCenterId));
        }
        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    @Override
    public long currentId() {
        return currentId;
    }

    /**
     * 当时间一致的时候，尽量保证序列号不一致
     * 但是对于序列号，一毫秒内最多也只能生成12位，如果超出了12位，就把时钟波动一毫秒，这样生成出来的数又不会一样呢
     * 但是对于时钟回拨，生成出来的随机数又可能一致呢，只能等待时钟恢复正常，等待一段时间后，还是没恢复正常，只能人为干预，程序抛出异常呢
     *
     * @return
     */
    @Override
    public synchronized long nextId() {
        long currentTimestamp = System.currentTimeMillis();

        if (currentTimestamp < lastTime) {
            long offset = lastTime - currentTimestamp;

            /* 如果时钟回拨在可容忍范围内 */
            if (offset <= MAX_BACKWARD_MS) {
                /* 时钟回拨,让当前时间追上去 */
                LockSupport.park(TimeUnit.MILLISECONDS.toNanos(offset));

                currentTimestamp = System.currentTimeMillis();

                if (currentTimestamp < lastTime) {
                    throw new RuntimeException(String.format("Close moved backwards. Refusing to generator Id"));
                }
            } else {
                throw new RuntimeException(String.format("Close moved backwards. Refusing to generator Id"));
            }
        }

        if (currentTimestamp > lastTime) {
            sequence = 0;
        } else if (currentTimestamp == lastTime) { // 当前毫秒内
            /* 出现溢出 */
            if (0L == (++sequence & maxSequence)) {
                currentTimestamp = waitNextTime(lastTime);
            }
        }

        lastTime = currentTimestamp;

        currentId = ((currentTimestamp - EPOCH) << timestampShift |
                workerId << workIdShift |
                dataCenterId << dataCenterShift |
                sequence);
        return currentId;
    }

    private long waitNextTime(long timestamp) {
        long currentTimestamp = System.currentTimeMillis();

        while (currentTimestamp < timestamp) {
            currentTimestamp = System.currentTimeMillis();
        }

        return currentTimestamp;
    }

    private long getWorkerId() {
        long workerId;
        try {
            String hostAddress = Inet4Address.getLocalHost().getHostAddress();
            int[] codePoints = StringUtils.toCodePoints(hostAddress);
            int sum = 0;
            for (int i : codePoints) {
                sum += i;
            }
            workerId = sum % 32;
        } catch (UnknownHostException e) {
            workerId = RandomUtils.nextLong(0, 31);
        }

        return workerId;
    }

    private long getDataCenterId() {
        String hostName = SystemUtils.getHostName();
        if (StringUtils.isEmpty(hostName)) {
            return RandomUtils.nextLong(0, 31);
        }

        int[] codePoints = StringUtils.toCodePoints(hostName);
        int sum = 0;
        for (int i : codePoints) {
            sum += i;
        }
        return sum % 32;
    }

    @Override
    public Map<Object, Object> info() {
        Map<Object, Object> info = new HashMap<>(4);
        info.put("currentId", currentId);
        info.put("workerId", workerId);
        return info;
    }
}
